<?php
/**
 * 对数组嵌套类型的值一次性检查数据库字段是否重复
 *
 * @author Zeon <scplay@gmail.com>
 * @date 2018/6/14
 * @copyright PanxSoft Inc.
 */
namespace App\Rules;

use App\Rules\Concerns\AttributeNormalize;
use Illuminate\Contracts\Validation\Rule;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\DB;

/**
 * Class ArrayDBUnique
 *
 * @package App\Rules
 */
class ArrayDBUnique implements Rule
{
    use AttributeNormalize;

    const VALIDATION_LOCALE_KEY = 'validation.array_db_unique';

    /**
     * @var string
     */
    protected $attribute;

    protected $message;

    private $all_data = [];

    private $checked = false;

    private $table;

    private $column;

    protected $using;

    protected $duplicate_vals = [];
    /**
     * @var bool
     */
    private $validate_null;

    /**
     * Create a new rule instance.
     *
     * @param array $all_data
     * @param string $table
     * @param string $column
     * @param bool $validate_null
     */
    public function __construct(array $all_data, string $table, string $column, $validate_null = false)
    {
        $this->all_data = $all_data;
        $this->table = $table;
        $this->column = $column;
        $this->validate_null = $validate_null;
        $this->using = function (Builder $query) {};
    }

    /**
     * Determine if the validation rule passes.
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @return bool
     */
    public function passes($attribute, $value)
    {
        if ($this->checked) {
            return true;
        }

        $asterisk_attr = $this->replaceIndexToAsterisks($attribute);

        // may force set
        $this->attribute = $this->attribute ?? $asterisk_attr;

        // WHERE
        //   spu_code IN ( "1", "2" ) <- 必须先转成 string
        //	 spu_code IN ( 1, 2 ) <- 如果 in 里面是 number 居然会把 数据库中 01 02 01M# 1abc 这种全匹配出来
        // 由于 request 经过了 ConvertEmptyStringsToNull
        $validate_vals = array_map([$this, 'stringify'], data_get($this->all_data, $asterisk_attr));

        // do not check null or empty string
        if (! $this->validate_null) {
            $validate_vals = array_filter($validate_vals);
        }

        $duplicates = DB::table($this->table)
            ->whereIn($this->column, $validate_vals)
            ->where($this->using)
            ->get([$this->column]);

        $this->duplicate_vals = array_unique($duplicates->pluck($this->column)->all());

        $this->checked = true;

        return ! count($this->duplicate_vals);
    }

    public function where(\Closure $callback)
    {
        $this->using = $callback;

        return $this;
    }

    /**
     * @example  'dev.12.uuid.3' => 'dev.*.uuid.*'
     *
     * @param $attr
     *
     * @return null|string|string[]
     */
    private function replaceIndexToAsterisks($attr)
    {
        return preg_replace('/\.\d+/', '.*', $attr);
    }

    private function stringify($val)
    {
        return (string) $val;
    }

    /**
     * Get the validation error message.
     *
     * @return string
     */
    public function message(): string
    {
        if ($this->message) return $this->message;

        $attr = $this->normalizeAttributeName($this->attribute);

        $duplicate_val_str = implode(',', $this->duplicate_vals);

        return trans(self::VALIDATION_LOCALE_KEY, [
            'attribute' => $attr,
            'values' => $duplicate_val_str,
            'table' => $this->table,
            'column' => $this->column,
        ]);
    }

    /**
     * force set attribute name
     *
     * @param string $name
     *
     * @return ArrayDBUnique
     */
    public function setAttribute(string $name): self
    {
        $this->attribute = $name;

        return $this;
    }

    /**
     * force set message
     *
     * @param string $message
     *
     * @return ArrayDBUnique
     */
    public function setMessage(string $message): self
    {
        $this->message = $message;

        return $this;
    }
}
